/*=============================================================================
#    The element-comm is a tentative program used to test our ideas.           
#                       Copyright (C)  bgao                                    
#
#     This program is free software; you can redistribute it and/or            
#      modify it under the terms of the GNU General Public License             
#     as published by the Free Software Foundation; either version 2           
#         of the License, or (at your option) any later version.               
#
#     This program is distributed in the hope that it will be useful,          
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            
#             GNU General Public License for more details.                    
#
#    You should have received a copy of the GNU General Public License        
#     along with this program; if not, write to the Free Software             
#      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,             
#                       MA  02110-1301, USA.                                  
#==============================================================================
# $Id: mac_recv.c 348 2010-11-29 09:01:16Z bgao $ 
#
# $Author: bgao $ 
#
# $Revision: 348 $ 
#
# $Date: 2010-11-29 17:01:16 +0800 (星期一, 29 十一月 2010) $ 
#
# Description: 
#
=============================================================================*/
/*! @file mac_recv.c
 *  @brief This file implements the main MAC control module, which 
 *         process the frame according to the parsing results.
 */
#include <stdio.h>
#include "mac.h"

static int8 mac_ctrl_handle_recv(mac_event_t event);
static void mac_timeout_assoc_req(int8 signum);
static void mac_timeout_wait_assoc(int8 signum);
static void mac_timeout_wait_poll(int8 signum);
static int8 mac_ctrl_handle_bcn(buf_t *buf, mac_hdr_t *hdr);
static int8 mac_ctrl_handle_ack(mac_hdr_t *hdr, mac_state_t state);
static int8 mac_ctrl_handle_cmd(buf_t *buf, mac_hdr_t *hdr);
static int8 mac_ctrl_handle_data(buf_t *buf, mac_hdr_t *hdr);
static int8 mac_ctrl_handle_assoc_req(buf_t *buf, mac_hdr_t *hdr);
static int8 mac_ctrl_handle_assoc_resp(buf_t *buf);
static int8 mac_handle_data_req(mac_hdr_t *hdr);
static int8 mac_ctrl_handle_bcn_req(void);

/*! bool flag indicates whether a frame is received */
static bool frm_recvd = false;

/* tentative: use to signal that frame is received */
/*! clear the frm_recvd flag.
 *  @return the value of frm_recvd.
 */
bool mac_clr_frm_recvd(void)
{
    return frm_recvd = false;
}

/*! set the frm_recvd flag.
 *  @return return the value of frm_recvd.
 */
bool mac_set_frm_recvd(void)
{
    return frm_recvd = true;
}

/*! get the value of frm_recvd.
 *  @return return the value of frm_recvd.
 */
bool mac_get_frm_recvd(void)
{
    return frm_recvd;
}

/* time out functions definitions */
/*! MAC association request timeout function, if the time wait for 
 *  a association request ack frame exceeds the maximum wait time,
 *  this function will be triggered.
 *  @param signum the parameter feed to the timeout function.
 */
static void mac_timeout_assoc_req(int8 signum)
{
    mac_poll_req_t req;
    /* here the handle filed is useless */
    //req.handle = 0;
    mac_poll_req(&req);

    mac_set_state(mac_assoc_poll_reqing);
}

/*! MAC wait association timeout function, if the time waiting for
 *  the association response frame exceeds the maximum wait time,
 *  this function will be triggered.
 *  @param signum the parameter feed to the timeout function.
 */  
static void mac_timeout_wait_assoc(int8 signum)
{
    mac_assoc_cfm_t cfm;
    uint16 short_addr;

    MAC_DEBUG("mac_timeout_wait_assoc: in it.\n");
    short_addr = mac_get_coord_short_addr();
    if (short_addr == USING_EXTENDED_ADDR) {
        cfm.addr.mode = ADDR_64_M;
        cfm.addr.a.addr64 = mac_get_coord_extended_addr();
    } else {
        cfm.addr.mode = ADDR_16_M;
        cfm.addr.a.addr16 = short_addr;
    }
    cfm.status = MAC_NO_DATA;
    mac_assoc_cfm(&cfm);

    mac_set_state(mac_idle);
}

/*! the MAC waiting poll timeout function, if the time waiting for 
 *  poll akc is exceeds the maximum time, this function will be
 *  triggered.
 *  @param signum the parameter feed to the timeout function.
 */
static void mac_timeout_wait_poll(int8 signum)
{
    mac_poll_cfm_t cfm;

    cfm.status = MAC_NO_DATA;
    mac_poll_cfm(&cfm);

    mac_set_state(mac_idle);
}

/*! handle the received beacon frame.
 *  @param buf pointer to the frame buffer.
 *  @param hdr pointer to the header structure of the received beacon.
 *  @return the result of the process.
 */
static int8 mac_ctrl_handle_bcn(buf_t *buf, mac_hdr_t *hdr)
{
    mac_bcn_ntf_t ntf;
    mac_frm_bcn_t bcn;
    mac_pan_desc_t *pd = &ntf.pan_desc;
    mac_fcs_t fcs;

    /* construct pan descriptor */ 
    mac_parse_bcn(buf, &bcn);
    /* put the dsn as bsn */
    ntf.bsn = hdr->dsn;
    pd->coord_pan_id = hdr->src_pan_id;
    pd->coord_addr = hdr->src_addr;
    pd->logic_chnl = rf_get_chnl();
    /* don't set pd->chnl_page */
    pd->sup_frm_spec_value = bcn.sup_frm_value;
    /* don't set pd->gts_pmit */
    /* link quality issue */
    mac_parse_fcs(buf, &fcs);
    pd->link_quality = fcs.rssi;
    /* no info about pend_addr_spec */
    /* no info about addr_list */
    /* no beacon payload now */

    /* TODO notify the next higher layer through beacon notification */
    return mac_bcn_ntf(&ntf);
}

/*! handle the ack frame.
 *  @param hdr pointer to the header strucuture.
 *  @param state current state of MAC.
 *  @return the result of this operation.
 */
static int8 mac_ctrl_handle_ack(mac_hdr_t *hdr, mac_state_t state)
{
    mtimer_t *timer = mac_mcb_get_timer();

    if (state == mac_assoc_reqing) {
        /* the mac_assoc_poll_reqing state should be set in the time out functions */
        /* start a timer to time out after macResponseWaitTime symbols */
        MAC_DEBUG("mac_ctrl_handle_ack: the current state is mac_assoc_reqing.\n");
        timer->func = mac_timeout_assoc_req;
        timer->expire = mac_get_response_wait_time();
        timer_init(timer, mac_timeout_assoc_req);
        timer_start(timer, timer->expire);
        MAC_DEBUG("mac_ctrl_handle_ack: expire time is %d.\n", timer->expire);
        MAC_DEBUG("mac_ctrl_handle_ack: finished timer setting.\n");


    } else if (state == mac_assoc_poll_reqing) {
        /* start a timer to time out after macMaxFrameTotalWaitTime symbols */
        MAC_DEBUG("mac_ctrl_handle_ack: now current state is mac_assoc_poll_reqing.\n");
        timer->func = mac_timeout_wait_assoc;
        timer->expire = mac_get_max_frame_total_wait_time() * 3; /* multiply by 3 for simulator only */
        timer_init(timer, mac_timeout_wait_assoc);
        timer_start(timer, timer->expire);
        MAC_DEBUG("mac_ctrl_handle_ack: expire time is %d.\n", timer->expire);

    } else if (state == mac_poll_reqing) {
        /* start a timer to time out after macResponseWaitTime symbols */
        timer->func = mac_timeout_wait_poll;
        timer->expire = mac_get_max_frame_total_wait_time();
        timer_init(timer, mac_timeout_wait_poll);
        timer_start(timer, timer->expire);

    } else {
        /* signal the nhl */
        mac_data_cfm_t cfm;
        mac_retry_t *retry;
        mac_indir_t *indir;

        retry = mac_retry_find(hdr->dsn);
        if (retry != NULL) {
            cfm.status = MAC_OK;
            cfm.handle = retry->handle;
            mac_data_cfm(&cfm);

        }

        indir = mac_indir_find_by_dsn(hdr->dsn);
        if (indir != NULL) {
            mac_indir_free(hdr->dsn);
        }
    }


    mac_retry_free(hdr->dsn);

    return MAC_OK;
}

/*! handle the MAC command frame.
 *  @param buf pointer to the buffer, in which the received frame is stored.
 *  @param hdr pointer to the parsed header structure.
 *  @return return the result of this operation.
 */
static int8 mac_ctrl_handle_cmd(buf_t *buf, mac_hdr_t *hdr)
{
    cmd_id_t cmd_id = mac_parse_cmd(buf);
    int8 r = MAC_ERR;

    if (cmd_id == CMD_ASSOC_REQ) {
        MAC_DEBUG("mac_ctrl_handle_cmd: got a assoc req.\n");
        r = mac_ctrl_handle_assoc_req(buf, hdr);

    } else if (cmd_id == CMD_ASSOC_RESP) {
        MAC_DEBUG("mac_ctrl_handle_cmd: got a assoc resq.\n");
        r = mac_ctrl_handle_assoc_resp(buf);

    } else if (cmd_id == CMD_DATA_REQ) {
        MAC_DEBUG("mac_ctrl_handle_cmd: got a data req.\n");
        r = mac_handle_data_req(hdr);

    } else if (cmd_id == CMD_BCN_REQ) {
        MAC_DEBUG("mac_ctrl_handle_cmd: got a beacon req.\n");
        r = mac_ctrl_handle_bcn_req();
    }

    return r;
}

/*! handle MAC association request frame.
 *  @param buf pointer to the buffer, in which the received frame is stored.
 *  @param hdr pointer to the parsed header structure.
 *  @return return the result of this operation.
 */
static int8 mac_ctrl_handle_assoc_req(buf_t *buf, mac_hdr_t *hdr)
{
    mac_frm_assoc_req_t req;
    mac_assoc_indict_t indict;

    mac_parse_assoc_req(buf, &req);
    /* construct association response, send indirect. */
    /* signal the nhl to make the final decision. */
    indict.src = hdr->src_addr;
    indict.cap_info = req.cap_info;
    return mac_assoc_indict(&indict);
}

/*! handle the association response frame.
 *  @param buf pointer to the buffer, in which the received frame is stored.
 *  @return return the result of this operation.
 */
static int8 mac_ctrl_handle_assoc_resp(buf_t *buf)
{
    mac_assoc_cfm_t cfm;
    mac_frm_assoc_resp_t resp;

    mac_parse_assoc_resp(buf, &resp);
    mac_set_state(mac_idle);
    /* signal the next higher layer */
    cfm.addr.mode = ADDR_16_M;
    cfm.addr.a.addr16 = resp.short_addr;
    cfm.status = resp.status;
    if (cfm.status == MAC_ASSOC_SUCCESS) {
        /* enable address recognition */
        uint16 pan_id = mac_get_pan_id();
        /* keep synchronize between MAC and RF */
        rf_set_short_addr(resp.short_addr);
        mac_set_short_addr(resp.short_addr);
        /* mac_pan_id has been setted */
        rf_set_pan_id(pan_id);
        rf_enable_addr_recg();
    } else {
        mac_set_pan_id(BROADCAST_PAN_ID);
        mac_set_short_addr(BROADCAST_ADDR);
    }
    
    return mac_assoc_cfm(&cfm);
}

/*! handle data request frame.
 *  @param hdr pointer to the parsed header structure.
 *  @return return the result of this operation.
 */
static int8 mac_handle_data_req(mac_hdr_t *hdr)
{
    int8 r;
    mac_indir_t *indir;
    /* check if there are any data for the requesting device */
    indir = mac_indir_find(&hdr->src_addr);
    if (indir == NULL) {
        mac_data_req_t req;
        buf_t *buf;
        buf = buf_alloc(true);
        if (buf == NULL) {
            r = MAC_TRANSACTION_OVERFLOW;
        }
        /* no actual data */
        req.src.mode = ADDR_16_M;
        req.src.a.addr16 = mac_get_short_addr();
        req.dest = hdr->src_addr;
        req.dest_pan_id = hdr->src_pan_id;
        req.opt.ack = 1;
        mac_data_req(buf, &req);
        buf_free(buf);

    } else {
        /* got it in the buffer, send the frame */
        uint8 en_retry = 1;
        uint8 en_indir = 0;
        /* don't signal the nhl if error occurs when tx */
        r = mac_handle_tx(indir->buf, &indir->addr, en_retry, en_indir, indir->handle, indir->dsn);
    }
    return r;
}

/*! handle the beacon request frame.
 *  @return return the result of this operation.
 */
static int8 mac_ctrl_handle_bcn_req(void)
{
    buf_t *buf;
    mac_frm_bcn_t bcn;
    addr_t addr;

    buf = buf_alloc(true);
    if (buf == NULL) {
        return MAC_ERR;
    }
    bcn.src.mode = ADDR_16_M;
    bcn.src.a.addr16 = mac_get_short_addr();
    bcn.pan_id = mac_get_pan_id();
    bcn.bsn = mac_get_dsn();
    bcn.sup_frm_value = mac_get_sup_frm_value();
    /* beacon response command */
    mac_gen_bcn(buf, &bcn);

    /* TODO should beacon be sent using retry? */
    addr.mode = NOT_PRESENT;
    mac_csmaca_tx(buf, bcn.bsn, false);
    MAC_DEBUG("mac_ctrl_handle_bcn_req: sent the beacon frame.\n");

    buf_free(buf);

    return MAC_OK;
}

/*! handle the MAC data frame.
 *  @param buf pointer to the buffer, in which the received frame is stored.
 *  @param hdr pointer to the parsed header structure.
 *  @return return the result of this operation.
 */
static int8 mac_ctrl_handle_data(buf_t *buf, mac_hdr_t *hdr)
{
    mac_data_indict_t indict;
    mac_fcs_t fcs;

    indict.src = hdr->src_addr;
    indict.src_pan_id = hdr->src_pan_id;
    indict.dest = hdr->dest_addr;
    indict.dest_pan_id = hdr->dest_pan_id;
    indict.dsn = hdr->dsn;

    mac_parse_fcs(buf, &fcs);
    indict.lqi = fcs.rssi;
    /* pass directly to the next higher layer */
    return mac_data_indict(buf, &indict);
}
 
/* TODO should be called by the frame receive interrupt */
/*! the entry point of MAC.
 *  @param event the type of event arrived.
 *  @return the result of this operation.
 */
int8 mac_entry(mac_event_t event)
{
    /* tentative approach */
    if (mac_get_frm_recvd()) {
        mac_clr_frm_recvd();
        return mac_ctrl_handle_recv(event);

    } else {
        return MAC_OK;
    }
}

/* if pan id is included, it should match macPANId or 0xffff */
/*! the top level MAC frame handle function, which is called by the
 *  mac_entry.
 *  @param event type of event arrived.
 *  @return return the result of this operation.
 */
static int8 mac_ctrl_handle_recv(mac_event_t event)
{
    buf_t *buf;
    mac_hdr_t hdr;
    mac_frm_ctrl_t *ctrl;
    mac_state_t state;
    int8 r;

    if (event != mac_event_frm_recv) {
        r = MAC_ERR;
        goto handle_recv_err_process;
    }

    MAC_DEBUG("mac_ctrl_handle_recv: in it.\n");
    buf = mac_queue_pop();
    if (buf == NULL) {
        r = MAC_ERR;
        goto handle_recv_err_process;
    }

    mac_parse_hdr(buf, &hdr);
    ctrl = &hdr.ctrl;
    /* TODO: with radio support auto acknowledgment set */
    if (ctrl->ack == 1) {
        MAC_DEBUG("mac_ctrl_handle_recv: send the ack.\n");
        mac_ack(hdr.dsn);
    }
    state = mac_get_state();
    MAC_DEBUG("mac_ctrl_handle_recv: the state of mac is %d.\n", state);
    if (state == mac_ed_scanning) {
        /* descard all frames */
        r = MAC_SCAN_IN_PROGRESS;
        goto handle_recv_err_process;

    } else if (state == mac_scanning) {
        if (ctrl->frm_type != FRM_TYPE_BCN) {
            /* descard all frames that are not beacons */
            r = MAC_SCAN_IN_PROGRESS;
            goto handle_recv_err_process;
        } else {
            MAC_DEBUG("mac_ctrl_handle_recv: got a beacon frame when ative scanning.\n");
            mac_ctrl_handle_bcn(buf, &hdr);
        }
    }
    MAC_DEBUG("mac_ctrl_handle_recv: the recved frame type is %d.\n", ctrl->frm_type);
    /* handle ack */
    if (ctrl->frm_type == FRM_TYPE_ACK) {
        MAC_DEBUG("mac_ctrl_handle_recv: got a ack frame.\n");
        r = mac_ctrl_handle_ack(&hdr, state);

    /* handle command */
    } else if (ctrl->frm_type == FRM_TYPE_CMD) {
        MAC_DEBUG("mac_ctrl_handle_recv: got a cmd frame.\n");
        r = mac_ctrl_handle_cmd(buf, &hdr);

    /* handle data */
    } else if (ctrl->frm_type == FRM_TYPE_DATA) {
        MAC_DEBUG("mac_ctrl_handle_recv: got a data frame.\n");
        mac_ctrl_handle_data(buf, &hdr);
    }

    /* check if there is another frame in the queue */ 
    if (!mac_queue_is_empty()) {
        mac_set_frm_recvd();
    }

    buf_free(buf);
    return MAC_OK;

handle_recv_err_process:
    buf_free(buf);
    return r;
}
